////////////////////////////////////////////////////////////////////////////////////////////////
// 
//
// Author: Miajun Yao
// Date: 11/25/2012
// 
////////////////////////////////////////////////////////////////////////////////////////////////



#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"

#include <stdio.h>
#include <math.h>
#include <string.h>

#include "ply.h"
#include "shaderSetup.h"
#include "GL/glew.h"
#include "GL/glut.h"
#include "GL/gl.h" 

#define M_PI 3.14159

//	define a vertex
typedef struct Vertex
{
  float location[4];
  float color[4]; 
  float normal[4];
  void* other;			//	reserved to store other attributes of the ply vertices
  Vertex()
  {
	  location[0] = 0; location[1] = 0; location[2] = 0; location[3] = 1;
	  normal[0] = 1; normal[1] = 0; normal[2] = 0; normal[3] = 0;
	  color[0] = 0.2; color[1] = 0.2; color[2] = 0.8; color[3] = 1;
	  other = NULL;
  }
} Vertex; 

//	define a face
typedef struct Face
{
  unsigned int count;
  unsigned int *vertices;
  void* other;			//	reserved to store other attributes of the ply vertices
} Face;

//	define a geometry shape
typedef struct
{
	int nindices;			//	number of indices
	GLuint vboHandle;		//	VBO that contain interleaved positions and colors 
	GLuint indexVBO;		//	index of the VBO
	//	no need to record the vertices and indices after they are passed to GPU VBO
} Geometry;

Geometry cubeFloor, cubeDemo, ply;

GLuint programObject1;			//	depthMapGeneration shader
GLuint programObject2;			//	shadowMapping shader

glm::mat4 modelMatrix;			//	the input modelView Matrix
glm::mat4 eyeViewMatrix;		//	view matrix for system which uses the eye as the camera position
glm::mat4 eyeProjectionMatrix;	//	projection matrix for system which uses the eye as the camera position
glm::mat4 lightViewMatrix;		//	view matrix for system which uses the light source as the camera position
glm::mat4 lightProjectionMatrix;//	projection matrix for system which uses the light source as the camera position
glm::mat4 biasLightProjectionMatrix;

glm::mat4 plyModelMatrix;

GLuint fboId;
GLuint depthMapId;				//	depth map

/////////////////////////////////
// glut mouse control 
// 
int xform_mode = 0; 
#define XFORM_NONE    0 
#define XFORM_ROTATE  1
#define XFORM_SCALE 2 

int press_x, press_y; 
int release_x, release_y; 
float x_angle = 0.0; 
float y_angle = 0.0; 
float scale_size = 1; 


//	return the length of a vector
float lengthVector(float* vector, int dimension)
{
	float length = 0;
	for (int i = 0; i < dimension; i++)
		length += vector[i] * vector[i];

	return sqrt(length);
}


//	create a VBO from a PlyFile, if no color specified in the ply file, a default color will be assigned to vertices
Geometry createPly(PlyFile* input)
{
	int i, j;
	Geometry ply;

	// go through the element types
	for(i = 0; i < input->num_elem_types; i = i + 1) 
	{
		int count;
		// setup the element for reading and get the element count
		char* element = setup_element_read_ply(input, i, &count);

		if (!element)
			continue;

		if(strcmp("vertex", element) == 0) 
		{
			Vertex* vertex = new Vertex[count];
			for(j = 0; j < input->elems[i]->nprops; j = j + 1) 
			{
				PlyProperty* property = input->elems[i]->props[j];
				PlyProperty setup;
				if(strcmp("x", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "x";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, location[0]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
				else if(strcmp("y", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "y";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, location[1]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
				else if(strcmp("z", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "z";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, location[2]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
				else if(strcmp("nx", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "nx";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, normal[0]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
				else if(strcmp("ny", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "ny";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, normal[1]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
				else if(strcmp("nz", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "nz";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, normal[2]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
				else if(strcmp("red", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "red";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, color[0]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
				else if(strcmp("green", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "green";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, color[1]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
				else if(strcmp("blue", property->name) == 0 && property->is_list == PLY_SCALAR) 
				{
					setup.name = "blue";
					setup.internal_type = Float32;
					setup.offset = offsetof(Vertex, color[2]);
					setup.count_internal = 0;
					setup.count_offset = 0;
					setup_property_ply(input, &setup);
				}
			}
			get_other_properties_ply(input, offsetof(Vertex,other));

			// find the center and size of the ply model
			float cx = 0, cy = 0, cz = 0;
			float min_x=9999, max_x=-9999, min_y=9999, max_y=-9999; 
			float min_z=9999, max_z=-9999; 

			// copy the data
			for(j = 0; j < count; j++) 
			{
				get_element_ply(input, (void*)(&vertex[j]));
				cx += vertex[j].location[0]; 
				cy += vertex[j].location[1]; 
				cz += vertex[j].location[2];
				if (min_x > vertex[j].location[0]) min_x = vertex[j].location[0]; 
				if (max_x < vertex[j].location[0]) max_x = vertex[j].location[0]; 
				if (min_y > vertex[j].location[1]) min_y = vertex[j].location[1]; 
				if (max_y < vertex[j].location[1]) max_y = vertex[j].location[1]; 
				if (min_z > vertex[j].location[2]) min_z = vertex[j].location[2]; 
				if (max_z < vertex[j].location[2]) max_z = vertex[j].location[2]; 
			}
			cx = cx / (float) count; 
			cy = cy / (float) count; 
			cz = cz / (float) count; 
			float plyScaleX = 4.0 / (max_x - min_x);
			float plyScaleY = 4.0 / (max_y - min_y);
			float plyScaleZ = 4.0 / (max_z - min_z);
			float plyMoveX = -cx;
			float plyMoveY = -min_y;
			float plyMoveZ = -cz;
			
			plyModelMatrix = glm::translate(glm::scale(glm::mat4(1.0f), glm::vec3(plyScaleX, plyScaleY, plyScaleZ)), glm::vec3(plyMoveX, plyMoveY, plyMoveZ));

			//	create VBO based on the vertices
			glGenBuffers(1, &ply.vboHandle);						// create an interleaved VBO object
			glBindBuffer(GL_ARRAY_BUFFER, ply.vboHandle);		// bind the first handle 
			glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * count, vertex, GL_STATIC_DRAW); // allocate space and copy the position data over
			glBindBuffer(GL_ARRAY_BUFFER, 0);   // clean up 

			delete []vertex;
		}
		else if(strcmp("face", element) == 0) 
		{
			//	if a face more than 3 vertices, only use the first 3
			ply.nindices = 3 * count; 
			GLuint *indices = new GLuint[ply.nindices]; 
			int offset = 0;

			for(j = 0; j < input->elems[i]->nprops; j = j + 1) 
			{
				PlyProperty* property = input->elems[i]->props[j];
				PlyProperty setup;

				if(strcmp("vertex_indices", property->name) == 0 && property->is_list == PLY_LIST) 
				{
					setup.name = "vertex_indices";
					setup.internal_type = Uint32;
					setup.offset = offsetof(Face, vertices);
					setup.count_internal = Uint32;
					setup.count_offset = offsetof(Face, count);
					setup_property_ply(input, &setup);
				}
				get_other_properties_ply(input, offsetof(Face, other));
				
				// copy the data
				for(j = 0; j < count; j++) 
				{
					Face* face = new Face();
					get_element_ply(input, (void*)(face));
					indices[offset++] = face->vertices[0];
					indices[offset++] = face->vertices[1];
					indices[offset++] = face->vertices[2];
				}
			}
			glGenBuffers(1, &ply.indexVBO); 
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ply.indexVBO); 
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * ply.nindices, indices, GL_STATIC_DRAW);  // load the index data 
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);				// clean up 

			delete[] indices;
		}

	}

	return ply;
}


//	create a cube mesh and create a VBO based on that
Geometry createCube(float size, float r, float g, float b) 
{
	Geometry cube;

	int nvertices = 24;										//	every vertex may have 3 normals with different facings
	Vertex* vertex = new Vertex[nvertices];		
	
	float vertexData[] = 
	{
		//	every line is a vertex with coordinate, normal and color
		size, size, size, 1.0,  1.0, 0.0, 0.0, 0.0,  r, g, b, 1.0,
		size, -size, size, 1.0,  1.0, 0.0, 0.0, 0.0,  r, g, b, 1.0,
		size, -size, -size, 1.0,  1.0, 0.0, 0.0, 0.0,  r, g, b, 1.0,
		size, size, -size, 1.0,  1.0, 0.0, 0.0, 0.0,  r, g, b, 1.0,

		size, size, size, 1.0,  0.0, 1.0, 0.0, 0.0,  r, g, b, 1.0,
		size, size, -size, 1.0,  0.0, 1.0, 0.0, 0.0,  r, g, b, 1.0,
		-size, size, -size, 1.0,  0.0, 1.0, 0.0, 0.0,  r, g, b, 1.0,
		-size, size, size, 1.0,  0.0, 1.0, 0.0, 0.0,  r, g, b, 1.0,

		size, size, size, 1.0,  0.0, 0.0, 1.0, 0.0,  r, g, b, 1.0,
		-size, size, size, 1.0,  0.0, 0.0, 1.0, 0.0,  r, g, b, 1.0,
		-size, -size, size, 1.0,  0.0, 0.0, 1.0, 0.0,  r, g, b, 1.0,
		size, -size, size, 1.0,  0.0, 0.0, 1.0, 0.0,  r, g, b, 1.0,

		-size, -size, -size, 1.0,  -1.0, 0.0, 0.0, 0.0,  r, g, b, 1.0,
		-size, -size, size, 1.0,  -1.0, 0.0, 0.0, 0.0,  r, g, b, 1.0,
		-size, size, size, 1.0,  -1.0, 0.0, 0.0, 0.0,  r, g, b, 1.0,
		-size, size, -size, 1.0, -1.0, 0.0, 0.0, 0.0,  r, g, b, 1.0,

		-size, -size, -size, 1.0,  0.0, -1.0, 0.0, 0.0,  r, g, b, 1.0,
		size, -size, -size, 1.0,  0.0, -1.0, 0.0, 0.0,  r, g, b, 1.0,
		size,- size, size, 1.0,  0.0, -1.0, 0.0, 0.0,  r, g, b, 1.0,
		-size, -size, size, 1.0,  0.0, -1.0, 0.0, 0.0,  r, g, b, 1.0,

		-size, -size, -size, 1.0,  0.0, 0.0, -1.0, 0.0,  r, g, b, 1.0,
		-size, size, -size, 1.0,  0.0, 0.0, -1.0, 0.0,  r, g, b, 1.0,
		size, size, -size, 1.0,  0.0, 0.0, -1.0, 0.0,  r, g, b, 1.0,
		size, -size, -size, 1.0,  0.0, 0.0, -1.0, 0.0,  r, g, b, 1.0,
	};

	for (int i = 0; i < nvertices; i++)
	{
		int rowSize = 12;
		int offset = i*rowSize;
		vertex[i].location[0] = vertexData[offset];vertex[i].location[1] = vertexData[offset+1];vertex[i].location[2] = vertexData[offset+2];vertex[i].location[3] = vertexData[offset+3];offset+=4;
		vertex[i].normal[0] = vertexData[offset];vertex[i].normal[1] = vertexData[offset+1];vertex[i].normal[2] = vertexData[offset+2];vertex[i].normal[3] = vertexData[offset+3];offset+=4;
		vertex[i].color[0] = vertexData[offset];vertex[i].color[1] = vertexData[offset+1];vertex[i].color[2] = vertexData[offset+2];vertex[i].color[3] = vertexData[offset+3];
		//	vertex[i].other = NULL;
	}

	//	set up indices
	int index[24];
	for (int i = 0; i < 24; i++)
		index[i] = i;
	cube.nindices = 24;

	//	create VBO based on the vertices and indices
	glGenBuffers(1, &cube.vboHandle);									// create an interleaved VBO object
	glBindBuffer(GL_ARRAY_BUFFER, cube.vboHandle);						// bind the first handle 
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * nvertices, vertex, GL_STATIC_DRAW);	// allocate space and copy the position data over
	glBindBuffer(GL_ARRAY_BUFFER, 0);   // clean up 

	glGenBuffers(1, &cube.indexVBO); 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube.indexVBO); 
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index), index, GL_STATIC_DRAW);  // load the index data 

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);  // clean up 
	delete[] vertex;

	return cube;
}

//	draw to depth map
void createDepthMap()
{
	glBindFramebuffer(GL_FRAMEBUFFER, fboId); 	
	glEnable(GL_DEPTH_TEST);		// need depth test to correctly draw 3D objects 
	glClear(GL_DEPTH_BUFFER_BIT); 
	glViewport(0, 0, 512, 512);		// the size of the viewport is the size of the texture 
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);


	glUseProgram(programObject1);
	glUniformMatrix4fv(glGetUniformLocation(programObject1, "lightViewMatrix"), 1, GL_FALSE, &lightViewMatrix[0][0]);
	glUniformMatrix4fv(glGetUniformLocation(programObject1, "lightProjectionMatrix"), 1, GL_FALSE, &lightProjectionMatrix[0][0]);

	//	draw ply
	glm::mat4 model = modelMatrix * plyModelMatrix;
	glUniformMatrix4fv(glGetUniformLocation(programObject1, "modelMatrix"), 1, GL_FALSE, &model[0][0]);
	GLint c0 = glGetAttribLocation(programObject1, "position"); 
	glEnableVertexAttribArray(c0); 
	glBindBuffer(GL_ARRAY_BUFFER, ply.vboHandle); 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ply.indexVBO); 
	glVertexAttribPointer(c0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, location[0]));
	glDrawElements(GL_TRIANGLES, ply.nindices, GL_UNSIGNED_INT, (char*) NULL+0); 
/*
	//	draw mirror
	model = glm::scale(glm::translate(modelMatrix, glm::vec3(8, 5, 0)), glm::vec3(0.2, 10, 10));
	glUniformMatrix4fv(glGetUniformLocation(programObject1, "modelMatrix"), 1, GL_FALSE, &model[0][0]);
	glBindBuffer(GL_ARRAY_BUFFER, cubeDemo.vboHandle); 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeDemo.indexVBO); 
	glVertexAttribPointer(c0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, location[0]));
	glDrawElements(GL_QUADS, cubeDemo.nindices, GL_UNSIGNED_INT, (char*) NULL+0);
*/
/*
	//	draw floor
	model = modelMatrix * glm::translate(glm::scale(glm::mat4(1.0f), glm::vec3(20.0f, 1.0f, 20.0f)), glm::vec3(0.0f, -0.5f, 0.0f));
	glUniformMatrix4fv(glGetUniformLocation(programObject1, "modelMatrix"), 1, GL_FALSE, &model[0][0]);
	c0 = glGetAttribLocation(programObject1, "position"); 
	glEnableVertexAttribArray(c0); 	
	glBindBuffer(GL_ARRAY_BUFFER, cubeFloor.vboHandle); 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeFloor.indexVBO); 
	glVertexAttribPointer(c0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, location[0]));
	glDrawElements(GL_QUADS, cubeFloor.nindices, GL_UNSIGNED_INT, (char*) NULL+0);
*/
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glBindFramebuffer(GL_FRAMEBUFFER, 0); 
}

void draw()
{
	glUseProgram(programObject2);

	glUniformMatrix4fv(glGetUniformLocation(programObject2, "lightViewMatrix"), 1, GL_FALSE, &lightViewMatrix[0][0]);
	glUniformMatrix4fv(glGetUniformLocation(programObject2, "lightProjectionMatrix"), 1, GL_FALSE, &biasLightProjectionMatrix[0][0]);
	glUniformMatrix4fv(glGetUniformLocation(programObject2, "eyeViewMatrix"), 1, GL_FALSE, &eyeViewMatrix[0][0]);
	glUniformMatrix4fv(glGetUniformLocation(programObject2, "eyeProjectionMatrix"), 1, GL_FALSE, &eyeProjectionMatrix[0][0]);
	glUniform1f(glGetUniformLocation(programObject2, "pixelOffset"), 1.0/512);

	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, depthMapId);
	glUniform1i(glGetUniformLocation(programObject2, "depthMap"), 2);
	
	glm::vec3 lightPos = glm::vec3(-10.0f, 10.0f, -10.0f);
	glUniform3fv(glGetUniformLocation(programObject2, "uLightPos"), 1, &lightPos[0]);

	//	draw ply
	glm::mat4 model = modelMatrix * plyModelMatrix;
	glUniformMatrix4fv(glGetUniformLocation(programObject2, "modelMatrix"), 1, GL_FALSE, &model[0][0]);
	GLint c0 = glGetAttribLocation(programObject2, "position"); 
	GLint c1 = glGetAttribLocation(programObject2, "color"); 
	GLint c2 = glGetAttribLocation(programObject2, "normal"); 
	glEnableVertexAttribArray(c0); 
	glEnableVertexAttribArray(c1); 
	glEnableVertexAttribArray(c2); 
	glBindBuffer(GL_ARRAY_BUFFER, ply.vboHandle); 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ply.indexVBO); 
	glVertexAttribPointer(c0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, location[0]));
	glVertexAttribPointer(c1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color[0]));
	glVertexAttribPointer(c2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal[0]));
	glDrawElements(GL_TRIANGLES, ply.nindices, GL_UNSIGNED_INT, (char*) NULL+0); 

	//	draw mirror
	model = glm::scale(glm::translate(modelMatrix, glm::vec3(5, 5, 0)), glm::vec3(0.2, 20, 20));
	glUniformMatrix4fv(glGetUniformLocation(programObject2, "modelMatrix"), 1, GL_FALSE, &model[0][0]);
	glBindBuffer(GL_ARRAY_BUFFER, cubeDemo.vboHandle); 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeDemo.indexVBO); 
	glVertexAttribPointer(c0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, location[0]));
	glVertexAttribPointer(c1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color[0]));
	glVertexAttribPointer(c2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal[0]));
	glDrawElements(GL_QUADS, cubeDemo.nindices, GL_UNSIGNED_INT, (char*) NULL+0);

	//	draw floor
	model = modelMatrix * glm::translate(glm::scale(glm::mat4(1.0f), glm::vec3(20.0f, 1.0f, 20.0f)), glm::vec3(0.0f, -0.5f, 0.0f));
	glUniformMatrix4fv(glGetUniformLocation(programObject2, "modelMatrix"), 1, GL_FALSE, &model[0][0]);
	c0 = glGetAttribLocation(programObject2, "position"); 
	c1 = glGetAttribLocation(programObject2, "color"); 
	c2 = glGetAttribLocation(programObject2, "normal"); 
	glEnableVertexAttribArray(c0); 
	glEnableVertexAttribArray(c1); 
	glEnableVertexAttribArray(c2); 	
	glBindBuffer(GL_ARRAY_BUFFER, cubeFloor.vboHandle); 
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeFloor.indexVBO); 
	glVertexAttribPointer(c0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, location[0]));
	glVertexAttribPointer(c1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color[0]));
	glVertexAttribPointer(c2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal[0]));
	glDrawElements(GL_QUADS, cubeFloor.nindices, GL_UNSIGNED_INT, (char*) NULL+0);

	glUseProgram(0);
}

void display() 
{ 
	glEnable(GL_DEPTH_TEST);    //	need depth test to correctly draw 3D objects 
	glClearColor(0,0,0,1);		
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 

	//	glut transformation parameters
	modelMatrix = glm::rotate(glm::mat4(1.0f), x_angle, glm::vec3(0.0f, 1.0f, 0.0f)); 
	modelMatrix = glm::rotate(modelMatrix, y_angle, glm::vec3(1.0f, 0.0f, 0.0f)); 
	modelMatrix = glm::scale(modelMatrix,  glm::vec3(scale_size, scale_size, scale_size)); 

	eyeViewMatrix = glm::lookAt(glm::vec3(-10.0, 10.0, 10.0), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));;
	eyeProjectionMatrix = glm::perspective(60.0f, 1.0f, .1f, 100.0f);

	lightViewMatrix = glm::lookAt(glm::vec3(-10.0, 10.0, -10.0), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));
	lightProjectionMatrix = glm::perspective(60.0f, 1.0f, .1f, 100.0f);
	
	// Moving from unit cube [-1,1] to [0,1] 
	glm::mat4 bias = glm::translate(glm::scale(glm::mat4(1.0f), glm::vec3(0.5, 0.5, 0.5)), glm::vec3(1.0, 1.0, 1.0));
	biasLightProjectionMatrix = bias * lightProjectionMatrix;

	createDepthMap();
	draw();

	glutSwapBuffers(); 
} 

void createFBO()
{
	glGenFramebuffers(1, &fboId); 
	glBindFramebuffer(GL_FRAMEBUFFER, fboId); 

	glGenTextures(1, &depthMapId); 
	glActiveTexture(GL_TEXTURE0); 
	glBindTexture(GL_TEXTURE_2D, depthMapId); 

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 

	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 512, 512, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);

	glDrawBuffer(GL_NONE); 

	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMapId, 0); 

	 // check FBO status
	GLenum FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
	if(FBOstatus != GL_FRAMEBUFFER_COMPLETE)
		printf("GL_FRAMEBUFFER_COMPLETE failed, CANNOT use FBO\n");

	glBindFramebuffer(GL_FRAMEBUFFER, 0); // go back to the default framebuffer  
}

//////////////////////////////////////////////////////////////////////////////////
//
//    GLUT stuff 
//
void mymouse(int button, int state, int x, int y)
{
	if (state == GLUT_DOWN) 
	{
		press_x = x; press_y = y; 
		if (button == GLUT_LEFT_BUTTON)
			xform_mode = XFORM_ROTATE; 
		else if (button == GLUT_RIGHT_BUTTON) 
			xform_mode = XFORM_SCALE;   
	}
	else if (state == GLUT_UP) 
	{
		xform_mode = XFORM_NONE; 
	}
}
void mymotion(int x, int y)
{
	if (xform_mode==XFORM_ROTATE) 
	{
		x_angle += (x - press_x)/5.0; 
		if (x_angle > 180) x_angle -= 360; 
		else if (x_angle <-180) x_angle += 360; 
		press_x = x; 
	   
		y_angle += (y - press_y)/5.0; 
		if (y_angle > 180) y_angle -= 360; 
		else if (y_angle <-180) y_angle += 360; 
		press_y = y; 
     }
	else if (xform_mode == XFORM_SCALE)
	{
		float old_size = scale_size;
		scale_size *= (1+ (y - press_y)/60.0); 
		if (scale_size <0) scale_size = old_size; 
		press_y = y; 
    }
    glutPostRedisplay(); 
}


///////////////////////////////////////////////////////////////////////////////
//
int main(int argc, char** argv) 
{ 
	glutInit(&argc, argv); 
	glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE|GLUT_DEPTH); 
	glutInitWindowSize(512,512); 

	glutCreateWindow("Shadow Mapping"); 
	glutDisplayFunc(display); 
	glutMouseFunc(mymouse); 
	glutMotionFunc(mymotion);

	//	initialize GLEW 
	GLenum err = glewInit(); 
	if ( err != GLEW_OK)  
	{
		printf("Error initializing GLEW! \n"); 
		exit(1);
	}
	
	//	create shaders
	programObject1 = SetupGLSL("depthMapGeneration");
	programObject2 = SetupGLSL("shadowMapping");


	//	create a PlyFile from a input .ply file
	PlyFile* input;
	FILE * pFile;
	printf("please specify your ply file: \n");
	char path[100];
	scanf("%s", path);
	pFile = fopen (path, "r");
	if (!pFile)
	{
		printf("Error opening .ply file! \n"); 
		exit(1);
	}
	
	// get the ply structure
	input = read_ply(pFile);
	if (!input)
	{
		printf("Error creating PlyFile! \n"); 
		exit(1);
	}

	//	create geometry VBOs
	cubeDemo = createCube(0.5, 0.9, 0.9, 0.8);
	cubeFloor = createCube(0.5, 0.8, 0.8, 0.8);
	ply = createPly(input);

	//	create FBO: depth map
	createFBO();

	glutMainLoop(); 
} 
